package sample.ui

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Color
import android.graphics.PixelFormat
import android.os.Build
import android.os.Process
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.view.WindowManager
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
import com.netease.core.R
import com.tendcloud.tenddata.TalkingDataSDK
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.script.Controller
import kotlinx.coroutines.script.DeepMethod
import kotlinx.coroutines.script.GComponents
import kotlinx.coroutines.script.tes.Accessibility.itc
import kotlinx.coroutines.script.tes.Accessibility.switchIntercept
import kotlinx.coroutines.script.tes.DiskLogUtils
import kotlinx.coroutines.script.tes.appContext
import kotlinx.coroutines.script.tes.appScopeLaunch
import kotlinx.coroutines.script.tes.dp
import kotlinx.coroutines.script.tes.isDebug
import kotlinx.coroutines.script.tes.lifecycleOwner
import kotlinx.coroutines.script.tes.observe
import kotlinx.coroutines.script.tes.phoneScreenWidth
import kotlinx.coroutines.script.tes.printLog
import kotlinx.coroutines.script.tes.pt
import kotlinx.coroutines.script.tes.setOnClickCallback
import kotlinx.coroutines.script.tes.toast
import v9.assist.initializeScreenshot
import kotlin.also
import kotlin.apply
import kotlin.let

class FloatControllerLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
    private val windowManager by lazy { context.getSystemService(Context.WINDOW_SERVICE) as WindowManager }
    private fun createWindowParams(x: Int, y: Int): WindowManager.LayoutParams {
        return WindowManager.LayoutParams().also {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                it.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                it.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
            }
            it.format = PixelFormat.RGBA_8888
            it.flags = (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                    or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                    or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
            it.gravity = Gravity.START or Gravity.TOP
            it.x = x
            it.y = y
            it.width = cw.toInt()
            it.height = ch.toInt()
        }.also { currentLp = it }
    }
    private var currentLp: WindowManager.LayoutParams? = null

    private val cw get() = if (displayStatus == "opened") 106f.dp else 32f.dp
    private val ch get() = 32f.dp
    private val container by lazy {
        LayoutInflater.from(context).inflate(R.layout.fcl, null)
    }
    private val switchSlopX = 160f.dp // x轴贴边切换边距离
    private val edge = 20f.dp
    private var floatStickyLeft = false
    private var edgeAnimator: ValueAnimator? = null
    private var displayStatus = "default" // "opened"

    private var lastStartClickTs = 0L

    init {
        windowManager.addView(this, createWindowParams((phoneScreenWidth - cw - edge).toInt(), 240.pt))
        addView(container, ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT))
        val imageView = container.findViewById<ImageView>(R.id.main)
        imageView.outlineProvider = ViewOutlineProvider.BACKGROUND
        imageView.clipToOutline = true

        val menuStart1 = container.findViewById<View>(R.id.menuStart1)
        val menuStart2 = container.findViewById<View>(R.id.menuStart2)
        menuStart1.setOnClickCallback {
            onStartClick()
        }
        menuStart2.setOnClickCallback {
            onStartClick()
        }
        val menuEnd1 = container.findViewById<View>(R.id.menuEnd1)
        val menuEnd2 = container.findViewById<View>(R.id.menuEnd2)
        menuEnd1.setOnClickCallback {
            toast("长按触发彻底退出")
        }
        menuEnd2.setOnClickCallback {
            toast("长按触发彻底退出")
        }
        menuEnd1.setOnLongClickListener {
            onEndLongClick()
            true
        }
        menuEnd2.setOnLongClickListener {
            onEndLongClick()
            true
        }
    }

    private fun onEndLongClick() {
        lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
            delay(500L)
            repeat(2) {
                toast("辅助即将超级大退")
                delay(800L)
            }
            Process.killProcess(Process.myPid())
        }
    }

    private fun onStartClick() {
        if (System.currentTimeMillis() - lastStartClickTs < 2500) return
        lastStartClickTs = System.currentTimeMillis()
        printLog { "controllerView click" }
        if (Controller.isRunning) {
            Controller.get().stop("")
            DiskLogUtils.saveToDisk()
        } else {
            RectLayout.Companion.hide()
            ConsoleLayout.markStoppedByScreenOff(false)
            GComponents.markNeedRefreshSkill()

            ConsoleLayout.initialize(context)
            DeepMethod.startController() // uiThread
        }
    }

    private fun onLongTap() {
        if (isDebug) { // 隐藏指令
            if (displayStatus == "default") {
                if (Properties.isControllerRunning) Controller.get().stop("")
                if (RectLayout.Companion.displaying) RectLayout.Companion.hide()
                else RectLayout.Companion.displayLine(context)
            } else if (displayStatus == "opened") {
                switchIntercept()
                val info = "已${if (itc) "关闭" else "开启"}指令下发"
                toast(info)
                printLog { info }
            }
        }
    }

    private fun onTap() {
        val main = container.findViewById<ImageView>(R.id.main)
        val menusLeft = container.findViewById<View>(R.id.menusLeft)
        val menusRight = container.findViewById<View>(R.id.menusRight)
        if (floatStickyLeft) {
            if (displayStatus == "opened") {
                main.updateLayoutParams<LayoutParams> {
                    startToStart = R.id.menusLeft
                    endToEnd =  LayoutParams.UNSET
                }
            } else {
                main.updateLayoutParams<LayoutParams> {
                    startToStart = LayoutParams.PARENT_ID
                    endToEnd = LayoutParams.PARENT_ID
                }
            }
            menusLeft.isVisible = displayStatus == "opened"
            menusRight.isVisible = false
        } else {
            if (displayStatus == "opened") {
                main.updateLayoutParams<LayoutParams> {
                    endToEnd = R.id.menusRight
                    startToStart = LayoutParams.UNSET
                }
            } else {
                main.updateLayoutParams<LayoutParams> {
                    startToStart = LayoutParams.PARENT_ID
                    endToEnd = LayoutParams.PARENT_ID
                }
            }

            menusRight.isVisible = displayStatus == "opened"
            menusLeft.isVisible = false
        }

        if (displayStatus == "opened" && alpha < 0.5f) {
            this@FloatControllerLayout.animate().alpha(1f).setDuration(400).start()
        }

    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        printLog { "FloatControllerLayout onAttachedToWindow" }
        TalkingDataSDK.onPageBegin(context, "Controller")
        setHasInstance(true)

        DescLayout.initialize(context)
        StatusLayout.Companion.initialize(context)
        keepScreenOn = true

        Controller.running.observe(this) {
            if (!it) {
                findViewById<ImageView>(R.id.menuStart1).setImageResource(R.mipmap.menu_start)
                findViewById<ImageView>(R.id.menuStart2).setImageResource(R.mipmap.menu_start)
            } else {
                findViewById<ImageView>(R.id.menuStart1).setImageResource(R.mipmap.menu_pause)
                findViewById<ImageView>(R.id.menuStart2).setImageResource(R.mipmap.menu_pause)
            }
        }
        Controller.clickStartStatus.observe(this) {
            findViewById<View>(R.id.startLoading1).isVisible = it
            findViewById<View>(R.id.startLoading2).isVisible = it

            if (!it && Controller.isRunning) {
                lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
                    delay(1000)
                    if (Controller.isRunning) {
                        displayStatus = "default"
                        slidingToEdge()
                        onTap()
                        delay(2000)
                        toast("使用音量键重新显示辅助悬浮球")
                        this@FloatControllerLayout.animate().alpha(0.15f).setDuration(400).start()
                    }
                }
            }
        }
        showEvent.observe(this) {
            it?.also {
                animate().alpha(1f).start()
            }
        }
        Controller.isInvalid.observe(this) {
            alpha = if (it == true) 0.3f else 1f
        }


        displayEvent.observe(this) {
            it?.also {
                visibility = if (it) VISIBLE else GONE
            }
        }
        lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
            initializeScreenshot(context)
        }
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        printLog { "FloatControllerLayout onDetachedFromWindow" }
        TalkingDataSDK.onPageEnd(context, "Controller")
        setHasInstance(false)
        keepScreenOn = false
        RectLayout.Companion.hide()
        ConsoleLayout.release()
        DescLayout.release()
        StatusLayout.Companion.release()
    }

    private val gestureDetector =
        GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
            override fun onDown(e: MotionEvent): Boolean {
                return true
            }

            override fun onScroll(
                e1: MotionEvent?,
                e2: MotionEvent,
                distanceX: Float,
                distanceY: Float
            ): Boolean {
                slidingToPosition((e2.rawX - cw / 2).toInt(), (e2.rawY - ch / 2).toInt())
                return true
            }

            override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
                displayStatus = if (displayStatus == "default") "opened" else "default"
                slidingToEdge()
                onTap()
                return false
            }

            override fun onLongPress(e: MotionEvent) {
                onLongTap()
                super.onLongPress(e)
            }
        })

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (event.actionMasked == MotionEvent.ACTION_UP) {
            slidingToEdge()
            onTap()
        }
        return gestureDetector.onTouchEvent(event)
    }

    private fun slidingToEdge() {
        releaseEdgeAnimator()

        val startX = currentLp?.x ?: 0
        val endX = if (floatStickyLeft && startX < switchSlopX) {
            edge
        } else if (floatStickyLeft && startX >= switchSlopX) {
            floatStickyLeft = false
            phoneScreenWidth - cw - edge
        } else if (!floatStickyLeft && startX > phoneScreenWidth - cw - switchSlopX) {
            phoneScreenWidth - cw - edge
        } else {
            floatStickyLeft = true
            edge
        }

        val startY = currentLp?.y ?: 0
        val endY: Float = when {
            floatStickyLeft && startY < 360.pt -> 360f.pt
            floatStickyLeft && startY > 500.pt -> 500f.pt
            !floatStickyLeft && startY < 240.pt -> 240f.pt
            !floatStickyLeft && startY > 280.pt -> 280f.pt
            else -> startY.toFloat()
        }

        edgeAnimator = ValueAnimator.ofFloat(1f, 0f)
            .apply {
                interpolator = AccelerateDecelerateInterpolator()
                duration = 300
                addUpdateListener { animation ->
                    (animation.animatedValue as? Float)?.let {
                        val animX = endX + (startX - endX) * it
                        val animY = endY + (startY - endY) * it
                        val lp = createWindowParams(animX.toInt(), animY.toInt())
                        windowManager.updateViewLayout(this@FloatControllerLayout, lp)
                    }
                }
            }
            .also { it.start() }
    }

    private fun releaseEdgeAnimator() {
        edgeAnimator?.cancel()
    }


    private fun slidingToPosition(endX: Int?, endY: Int?) {
        if (endX == null || endY == null) return
        if (edgeAnimator?.isRunning == true) return
        val startX = currentLp?.x ?: 0
        val startY = currentLp?.y ?: 0
        edgeAnimator = ValueAnimator.ofFloat(1f, 0f)
            .apply {
                interpolator = AccelerateDecelerateInterpolator()
                duration = 160
                addUpdateListener { animation ->
                    (animation.animatedValue as? Float)?.let {
                        val animX = endX + (startX - endX) * it
                        val animY = endY + (startY - endY) * it
                        val lp = createWindowParams(animX.toInt(), animY.toInt())
                        windowManager.updateViewLayout(this@FloatControllerLayout, lp)
                    }
                }
            }
            .also { it.start() }
    }

    companion object {
        val displayEvent = MutableLiveData<Boolean?>()
        fun notifyDisplay(value: Boolean) {
            DescLayout.notifyUpdate()
            StatusLayout.Companion.notifyUpdate()
            displayEvent.value = value
            displayEvent.value = null
        }
        private val updateEvent = MutableLiveData<Any?>()
        fun notifyUpdate() {
            DescLayout.notifyUpdate()
            StatusLayout.Companion.notifyUpdate()
            updateEvent.value = Any()
            updateEvent.value = null
        }
        val showEvent = MutableLiveData<Boolean?>()
        private var tsFilterFrequency = 0L
        fun notifyShow(value: Boolean) {
            if (System.currentTimeMillis() - tsFilterFrequency < 1000) return
            printLog { "Show notifyShow alpha = 1" }
            showEvent.value = value
            showEvent.value = null
            tsFilterFrequency = System.currentTimeMillis()
        }

        private var hasInstance = false
        private fun setHasInstance(value: Boolean) {
            hasInstance = value
        }

        fun initialize(context: Context) {
            if (Permissions.isCaptureAbilityEnable
                && Permissions.isAccessibilityPermissionOpened) {
                if (!hasInstance) FloatControllerLayout(context)
            }
        }

        val displayUserAnalysis get() = displayAnalysis
        private var displayAnalysis = false // false //(Build.VERSION_CODES.P == Build.VERSION.SDK_INT)
        fun setDisplayAnalysis(value: Boolean) {
            displayAnalysis = value
            // Unknown window type: 2999
            val info = if (value) "视觉反馈已开启" else "视觉反馈已关闭"
            toast(info)
            ConsoleLayout.outputText(info, Color.YELLOW)
            appScopeLaunch(Dispatchers.Main) {
                if (!value) {
                    SmallMapLayout.Companion.release()
                }
                else {
                    SmallMapLayout.Companion.initialize(appContext)
                }
            }
        }
    }
}
